package edu.siu.math.egut.util;


import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;




/**
 * A data structure for more complicated applications.
 * See, for example, SectionIntegral.
 * 
 * @author Joe Pleso
 *
 */
public class Piece implements Cloneable {

    public Object clone() throws CloneNotSupportedException {
	Piece p = (Piece)super.clone();
	p.exponents = new TreeMap<String, LaurentPolynomial>(p.exponents);
	p.isInteger = new TreeMap<String, Boolean>(p.isInteger);
	p.n = (ExceptionalGroupElement) n.clone();
	/*@@*/
	
	return p;
    }
    


    
    private final List<LaurentPolynomial> toralExponent;
    private final List<String> variables;
    private Map<String, LaurentPolynomial> exponents;
    private Map<String, Boolean> isInteger;
    private ExceptionalGroupElement n; 
    private LaurentPolynomial psiFactor; 
    
    
    /**
     * Creates an integral.
     * 
     * 
     * 
     * @param toralExponent the powers that pop out when a toral element reaches
     * the left side of f_s
     * @param n the unipotent
     * @param psiFactor the argument of psi
     */
    public Piece(
	    final List<LaurentPolynomial> toralExponent,
	    final  ExceptionalGroupElement n, 
	    final LaurentPolynomial psiFactor) { 
	/*@@*/
	this.toralExponent = toralExponent;
	this.n = n;
	this.psiFactor = psiFactor;
	/*@@*/
	
	this.isInteger = new TreeMap<String, Boolean>();
	this.exponents = new TreeMap<String, LaurentPolynomial>();
	variables = new ArrayList<String>();
	for (int i = 0; i < n.getNumberOfTerms(); i++) {
	    variables.add(n.getPolynomial(i).getVar());
	}
	for (String var : psiFactor.getVariables()) {
	    isInteger.put(var, Boolean.TRUE);
	}
	for (String var : variables) {
	    isInteger.remove(var);
	}
    }
    
    
   /**
 * Performs repeated bifrucations until each piece is an integral containing
 * at most one 1-psg. 
 * 
 * @return the list of integrals
 */

    public  List<Piece> reduce() {
       List<Piece> result = new ArrayList<Piece>();
       result.add(this);
       int i = 0;
       while(true) {
	  
	   for (i = 0; i < result.size(); i++) {
	       Piece p = result.get(i);
	       if (p.n.getNumberOfTerms() != 1) {
		   
		   break;
	       }
	   }
	   if (i == result.size())
	       break;
	   Piece p = result.get(i);
	   result.remove(i);
	   
	   Piece[] bi = p.bifurcate();
	   result.add(bi[0]);
	   result.add(bi[1]);
       }
    
       return result;
   }



    Piece[] bifurcate() {/*@\label{pbifurcate}@*/
	Piece[] result = new Piece[2];
	String var = n.getPolynomial(n.getNumberOfTerms() - 1).getVar();
	try {
	    result[0] = (Piece) this.clone();
	    result[1] = (Piece) this.clone();
	} 
	catch (CloneNotSupportedException e1) {
	    throw new RuntimeException(e1);
	}

	//change the domain of integration on the two cloned pieces
	

	result[0].isInteger.put(var,Boolean.TRUE);
	result[1].isInteger.put(var,Boolean.FALSE);
	//drop the appropriate terms from the psifactor
	for (Entry<Term, Integer> e : result[0].psiFactor.getTerms()){
	    Term t = e.getKey();
	    boolean isInt = true;
	    for(String x : t.getVariables()){
		if (!result[0].isInteger.containsKey(x) 
			|| !result[0].isInteger.get(x))
		    isInt = false;
	    }
	    if (isInt)
	    result[0].psiFactor = result[0].psiFactor.remove(t);
	}
	//change n^- in the one piece and use iwasawa decomp. on the other
	result[0].n.remove(result[0].n.getNumberOfTerms() - 1);
	result[1].iwasawa();

	//use left invar. prop. to get rid of toral part
	List<LaurentPolynomial> toral =  result[1].n.popToralPart();
	//calculate exponent of the variable
	LaurentPolynomial factor;
	if (!result[1].exponents.containsKey(var))
	    factor = null;
	else
	    factor = result[1].exponents.get(var);
	for (int i = 0; i < n.getGroup().getRank(); i++) {
	    LaurentPolynomial next = LaurentPolynomial.create(
		    LaurentPolynomial.log(toral.get(i))).times(
			    this.toralExponent.get(i));
	    if (factor != null && LaurentPolynomial.log(toral.get(i)) != 0)
	        factor = factor.plus(next);
	    else if (factor == null && LaurentPolynomial.log(toral.get(i)) != 0)
		factor = next;
	  
	}
	//apply change of variable to psifactor   
	LinkedList<LaurentPolynomial> replacements = 
	    new LinkedList<LaurentPolynomial>();
	for (int i = 0; i < result[1].n.getNumberOfTerms(); i++) {
	    factor = factor.plus(
		    LaurentPolynomial.create(-LaurentPolynomial.log(

			    result[1].n.getPolynomial(i).divide(n.getPolynomial(
				    i)))));
	    replacements.add(LaurentPolynomial.create(variables.get(i)).divide(
			    result[1].n.getPolynomial(i).divide(n.getPolynomial(
				    i))));

	    result[1].n.replace(variables.get(i), replacements.get(i));
	    result[1].psiFactor = result[1].psiFactor.replace(
		
		    variables.get(i),
		    replacements.get(i));
	}
	/*@%//System.out.println(result[1].n);
	 @*/
	
	result[1].exponents.put(var,factor); 
	//prepare to push sunipotents around
	List<Integer> saved = new ArrayList <Integer>();
	for (int i = 0; i < result[1].n.getNumberOfTerms() - 1; i++) {
	    saved.add(n.getGroup().findIndex(result[1].n.getRoot(i)));
	}
	//pushing algorithm
	
	/*System.out.println(result[1].writeTexFile());
	System.out.println(result[1].n.toTex());*/
	List<Integer> allowed = new ArrayList <Integer>();
        result[1].n.pushLeft(result[1].n.getNumberOfTerms() - 1);
        /*System.out.println(result[1].n.toTex());*/
        do {
            allowed.clear();
            allowed.addAll(saved);
	    
	    for (int i = 0; i < result[1].n.getNumberOfTerms(); i++) {
		Integer curRoot = n.getGroup().findIndex(
			result[1].n.getRoot(i));
		if (!allowed.remove(curRoot)) {
		    if (i == 0)
			result[1].n.remove(i);
		    else
			result[1].n.pushLeft(i);
		    /*System.out.println(result[1].n.toTex());*/
		    break;
		    
		}
		
	    }
	    
            
            
	   
	  /*@%//  n.pushLeft(scanner.nextInt());
	   @*/
	} while(!allowed.isEmpty());
        
	//start second change of variable
        replacements.clear();
        for (int i = 0; i < result[1].n.getNumberOfTerms() ; i++) {
	
            
            boolean isPositive = result[1].n.getPolynomial(i).getCoefficient(
		    Term.create(variables.get(i))) > 0;
            if (isPositive)
        	replacements.add(result[1].n.getPolynomial(i).remove(
        		Term.create(variables.get(i))).opposite().plus(
        			Term.create(variables.get(i))));
            else
        	replacements.add(result[1].n.getPolynomial(i).remove(
        		Term.create(variables.get(i))).plus(
        			Term.create(variables.get(i))));
            
	}
       
        int i = 0;
        for (LaurentPolynomial lp : replacements){
            /*System.out.println(variables.get(i));
            System.out.println(lp);*/
            
            result[1].n.replace(variables.get(i), lp);
            result[1].psiFactor = result[1].psiFactor.replace(
        	    variables.get(i),
            	    lp);
            i++;
        }
        
	
	
        /*@%//System.out.println(result[1].n);
         @*/
	return result;
    }/*@\label{pbifurcateend}@*/
    
    
    private void iwasawa() {/*@\label{piwasawa}@*/
	int[] root = n.getRoot(n.getNumberOfTerms() - 1);
	LaurentPolynomial v = n.getPolynomial(n.getNumberOfTerms() - 1);
	n.remove(n.getNumberOfTerms() - 1);
	LaurentPolynomial posV;
	if (v.getCoefficient(v.getTerm()) < 0)
	    posV = v.opposite();
	else 
	    posV = v;
	/*@%//System.out.println(v.toString());
	 @*/
	try {
	    n = ExceptionalGroupElement.times(n,n.getGroup().h(
		    n.getGroup().negative(root),posV.inverse()));
	
	    n = ExceptionalGroupElement.times(n,n.getGroup().chi(
		    n.getGroup().negative(root),v));
	} catch (NotARootException e) {
	    throw new RuntimeException(e);
	}
	
    }/*@\label{piwasawaend}@*/




    /**
     * Returns a TeX representation of this object.
     * 
     * @return a TeX representation of this object
     */
    public String writeTexFile() {/*@\label{pwriteTexFile}@*/
	StringBuffer sb = new StringBuffer();
	/*@@*/
	sb.append("I_{");
	for (int i = variables.size() - 1; i >= 0; i--) {
	    Boolean isInt = isInteger.get(variables.get(i));
	    if (isInt != null) {
		if (isInt) {
		    sb.append("+");
		}
		else {
		    sb.append("-");
		}
	    }
	}
	sb.append("}&=\\int_{\\{ ");
	for (int i = variables.size() - 1; i >= 0; i--) {
	    Boolean isInt = isInteger.get(variables.get(i));
	    if (isInt != null) {
		if (!isInt) {
		    sb.append(variables.get(i));
		    sb.append(",");
		}
		else{
		    /*@@*/
		}
	    }
	}
	/*@%//if (sb.charAt(sb.length()-1) == '{'){
	   %//   sb.setCharAt(sb.length()-1, ' ');
	   %//}else
	   %{
	@*/
	sb.setCharAt(sb.length()-1, ' ');
	sb.append("\\}");
	/*@%}
	@*/
	sb.append("}^{\\{ ");
	for (int i = variables.size() - 1; i >= 0; i--) {
	    Boolean isInt = isInteger.get(variables.get(i));
	    if (isInt != null) {
		if (isInt) {
		    sb.append(variables.get(i));
		    sb.append(",");
		}
		else{
		    /*@@*/
		}
	    }
	}
	/*@%//if (sb.charAt(sb.length()-1) == '{'){
	   %//   sb.setCharAt(sb.length()-1, ' ');
	   %//}else
	   %{
	   @*/
	sb.setCharAt(sb.length()-1, ' ');
	sb.append("\\}");
	/*@%}
	@*/
	sb.append("}&&f_s\\bigl(n^-( ");
	for (int i = 0; i < n.getNumberOfTerms(); i++) {
	    sb.append(n.getPolynomial(i));
	    sb.append(",");
	}
	/*@@*/
	sb.setCharAt(sb.length()-1, ')');
	sb.append("\\bigr)");
	for (int i = variables.size() - 1; i >= 0; i--) {
	    if (exponents.get(variables.get(i)) != null) {
		sb.append("\\lvert ");
		sb.append(variables.get(i));
		sb.append("\\rvert ");
		if (exponents.get(variables.get(i))
			!= LaurentPolynomial.create(1)) {
		    sb.append("^{ ");
		    sb.append(exponents.get(variables.get(i)));
		    sb.append("} ");
		}
	    }
	}
	sb.append("\\psi(");
	sb.append((this.psiFactor));
	sb.append(")\\,dn,\\\\");
	/*@@*/    
	return sb.toString();
    }/*@\label{pwriteTexFileend}@*/
    
    /**
     * Test function.
     * 
     * @param aa this is ignored
     * @throws NotARootException thrown if pigs are flying
     * 
     */
    public static void main(String[] aa) throws NotARootException {/*@\label{pmain}@*/
	ExceptionalGroup g = ExceptionalGroup.getGroup(2);
	//Set up left invariance of toral elements
	LaurentPolynomial s = LaurentPolynomial.create("s");
	LaurentPolynomial three = LaurentPolynomial.create(3);
	LaurentPolynomial six = LaurentPolynomial.create(6);
	List<LaurentPolynomial> exps = new ArrayList<LaurentPolynomial>();
	exps.add(six.times(s));
	exps.add(three.times(s));
	//get list of roots

	
	int [][] roots = g.getRoots();

	//get primitive weyl elements
	WeylElement wAlpha = new WeylElement(g, 0);
	WeylElement wBeta = new WeylElement(g, 1);
	WeylElement product = (WeylElement) wAlpha.copy();
	//build wABABA
	product = product.times(wBeta);
	product = product.times(product);
	product = product.times(wAlpha);//wABABA
	
	/*@%System.out.println(product) //how many escapes are we in?!
	@*/
	LaurentPolynomial v = LaurentPolynomial.create("v");
	LaurentPolynomial x = LaurentPolynomial.create("x");
	LaurentPolynomial u = LaurentPolynomial.create("u");
	ExceptionalGroupElement n = g.chi(roots[4], x.opposite());
	n = ExceptionalGroupElement.times(
	    n,
	    g.chi(roots[3], LaurentPolynomial.create("y")));
	n = ExceptionalGroupElement.times(
	    n,
	    g.chi(roots[5], LaurentPolynomial.create("z").opposite()));
	n = ExceptionalGroupElement.times(n, g.chi(roots[2], u.opposite()));
	n = ExceptionalGroupElement.times(n, g.chi(roots[1], v));
	n = ExceptionalGroupElement.times(product, n);
	LaurentPolynomial a = LaurentPolynomial.create("a");
	LaurentPolynomial bvar = LaurentPolynomial.create("b");
	LaurentPolynomial c = LaurentPolynomial.create("c");
	Piece p;
	
	boolean IS_HARD = false;//switch for the ``hard'' integral
	if (IS_HARD){
	    p = new Piece(exps, n, c.times(v).plus(bvar.times(u)).plus(x));
	}
	else{
	    p = new Piece(exps, n, a.times(v).plus(x));
	}
	List<Piece> b = p.reduce();
	for (Piece next : b) {
	    System.out.println(next.writeTexFile());
	}
	
    }/*@\label{pmainend}@*/
    
}
